<!DOCTYPE HTML>
<html>
	<head>
		<title>bitsy editor</title>

		<style>
			body {
				background: #ccccff;
				line-height: 150%;
				font-family: "Gill Sans", Arial, sans-serif;
				font-size: 15px;
			}
			canvas {
				background: black;
			}
			#topbar {
				width: 100%;
			}
			.panel {
				float:left;
				background: white;
				margin:10px;
				padding: 10px;
			}
			textarea {
				font-family: monospace;
			}
			button {
				font-family: "Gill Sans", Arial, sans-serif;
				font-size: 12px;
			}
			input {
				font-family: monospace;
			}
		</style>

		<script src="bitsy.js"></script>
		<script>
			/* MOES */
			var TileType = {
				Tile : 0,
				Sprite : 1,
				Avatar : 2
			};
			var EditMode = {
				Edit : 0,
				Play : 1
			};

			var editMode = EditMode.Edit;

			/* PAINT */
			var paint_canvas;
			var paint_ctx;
			var paint_scale = 32;

			var paintMode = TileType.Avatar;
			var drawingId = "A";
			var drawingPal = "0";
			var drawing_data = [
				[0,0,0,0,0,0,0,0],
				[0,0,0,0,0,0,0,0],
				[0,0,0,0,0,0,0,0],
				[0,0,0,0,0,0,0,0],
				[0,0,0,0,0,0,0,0],
				[0,0,0,0,0,0,0,0],
				[0,0,0,0,0,0,0,0],
				[0,0,0,0,0,0,0,0]
			];
			var drawPaintGrid = true;
			var curPaintBrush = 0;
			var isPainting = false;

			var nextTileCharCode = 97;
			var tileIndex = 0;
			var nextSpriteCharCode = 97;
			var spriteIndex = 0;

			/* MAP */
			var drawMapGrid = true;

			function start() {
				//game canvas & context (also the map editor)
				canvas = document.getElementById("game");
				canvas.width = width * scale;
				canvas.height = width * scale;
				ctx = canvas.getContext("2d");
				//map edit events
				listenMapEditEvents();

				//paint canvas & context
				paint_canvas = document.getElementById("paint");
				paint_canvas.width = tilesize * paint_scale;
				paint_canvas.height = tilesize * paint_scale;
				paint_ctx = paint_canvas.getContext("2d");
				//paint events
				paint_canvas.addEventListener("mousedown", paint_onMouseDown);
				paint_canvas.addEventListener("mousemove", paint_onMouseMove);
				paint_canvas.addEventListener("mouseup", paint_onMouseUp);
				paint_canvas.addEventListener("mouseleave", paint_onMouseUp);


				//default values
				title = "Write title here";
				palette[drawingPal] = [
					[0,82,204],
					[128,159,255],
					[255,255,255]
				];
				//default avatar
				paintMode = TileType.Avatar;
				on_paint_avatar();
				drawing_data = [
					[0,0,0,1,1,0,0,0],
					[0,0,0,1,1,0,0,0],
					[0,0,0,1,1,0,0,0],
					[0,0,1,1,1,1,0,0],
					[0,1,1,1,1,1,1,0],
					[1,0,1,1,1,1,0,1],
					[0,0,1,0,0,1,0,0],
					[0,0,1,0,0,1,0,0]
				];
				saveDrawingData();
				sprite["A"].set = "0";
				sprite["A"].x = 4;
				sprite["A"].y = 4;
				//defualt sprite
				paintMode = TileType.Sprite;
				newSprite();
				on_paint_sprite();
				drawing_data = [
					[0,0,0,0,0,0,0,0],
					[0,0,0,0,0,0,0,0],
					[0,1,0,1,0,0,0,1],
					[0,1,1,1,0,0,0,1],
					[0,1,1,1,0,0,1,0],
					[0,1,1,1,1,1,0,0],
					[0,0,1,1,1,1,0,0],
					[0,0,1,0,0,1,0,0]
				];
				saveDrawingData();
				sprite["a"].set = "0";
				sprite["a"].x = 8;
				sprite["a"].y = 12;
				dialog["a"] = "I'm a cat";
				//default tile
				paintMode = TileType.Tile;
				newTile();
				on_paint_tile();
				drawing_data = [
					[1,1,1,1,1,1,1,1],
					[1,0,0,0,0,0,0,1],
					[1,0,0,0,0,0,0,1],
					[1,0,0,1,1,0,0,1],
					[1,0,0,1,1,0,0,1],
					[1,0,0,0,0,0,0,1],
					[1,0,0,0,0,0,0,1],
					[1,1,1,1,1,1,1,1]
				];
				saveDrawingData();
				renderImages();
				//default set
				set["0"] = {
					id : "0",
					tilemap : [
							"0000000000000000",
							"0aaaaaaaaaaaaaa0",
							"0a000000000000a0",
							"0a000000000000a0",
							"0a000000000000a0",
							"0a000000000000a0",
							"0a000000000000a0",
							"0a000000000000a0",
							"0a000000000000a0",
							"0a000000000000a0",
							"0a000000000000a0",
							"0a000000000000a0",
							"0a000000000000a0",
							"0a000000000000a0",
							"0aaaaaaaaaaaaaa0",
							"0000000000000000"
						],
					walls : [],
					exits : [],
					pal : null
				};
				refreshGameData();

				//draw everything
				on_paint_avatar();
				drawPaintCanvas();
				drawEditMap();

				//load engine for export
				loadEngineScript();
			}

			function listenMapEditEvents() {
				canvas.addEventListener("mousedown", map_onMouseDown);
			}

			function unlistenMapEditEvents() {
				canvas.removeEventListener("mousedown", map_onMouseDown);
			}

			function newTile() {
				drawingId = String.fromCharCode( nextTileCharCode );
				nextTileCharCode++;

				drawing_data = [
					[0,0,0,0,0,0,0,0],
					[0,0,0,0,0,0,0,0],
					[0,0,0,0,0,0,0,0],
					[0,0,0,0,0,0,0,0],
					[0,0,0,0,0,0,0,0],
					[0,0,0,0,0,0,0,0],
					[0,0,0,0,0,0,0,0],
					[0,0,0,0,0,0,0,0]
				];

				drawPaintCanvas();
				saveDrawingData();
				refreshGameData();

				tileIndex = Object.keys(tile).length - 1;

				reloadTile(); //hack for ui consistency
			}

			function nextTile() {
				var ids = sortedTileIdList();
				tileIndex = (tileIndex + 1) % ids.length;
				drawingId = ids[tileIndex];
				reloadTile();
			}

			function prevTile() {
				var ids = sortedTileIdList();
				tileIndex = (tileIndex - 1) % ids.length;
				if (tileIndex < 0) tileIndex = (ids.length-1);
				drawingId = ids[tileIndex];
				reloadTile();
			}

			function newSprite() {
				drawingId = String.fromCharCode( nextSpriteCharCode );
				nextSpriteCharCode++;

				drawing_data = [
					[0,0,0,0,0,0,0,0],
					[0,0,0,0,0,0,0,0],
					[0,0,0,0,0,0,0,0],
					[0,0,0,0,0,0,0,0],
					[0,0,0,0,0,0,0,0],
					[0,0,0,0,0,0,0,0],
					[0,0,0,0,0,0,0,0],
					[0,0,0,0,0,0,0,0]
				];

				drawPaintCanvas();
				saveDrawingData();
				refreshGameData();

				spriteIndex = Object.keys(sprite).length - 1;

				reloadSprite(); //hack
			}

			function nextSprite() {
				var ids = sortedSpriteIdList();
				spriteIndex = (spriteIndex + 1) % ids.length;
				drawingId = ids[spriteIndex];
				reloadSprite();
			}

			function prevSprite() {
				var ids = sortedSpriteIdList();
				spriteIndex = (spriteIndex - 1) % ids.length;
				if (spriteIndex < 0) spriteIndex = (ids.length-1);
				drawingId = ids[spriteIndex];
				reloadSprite();
			}

			function next() {
				if (paintMode == TileType.Tile) {
					nextTile();
				}
				else {
					nextSprite();
				}
			}

			function prev() {
				if (paintMode == TileType.Tile) {
					prevTile();
				}
				else {
					prevSprite();
				}
			}

			function newDrawing() {
				if (paintMode == TileType.Tile) {
					newTile();
				}
				else {
					newSprite();
				}
			}

			function reloadTile() {
				var drw = "TIL_" + drawingId;
				for (y in imageStore.source[drw]) {
					for (var x = 0; x < 8; x++) {
						var pixel = parseInt( imageStore.source[drw][y].charAt(x) );
						drawing_data[y][x] = pixel;
					}
				}
				drawPaintCanvas();

				if (set[curSet]) {	
					if (set[curSet].walls.indexOf(drawingId) != -1) {
						document.getElementById("wallCheckbox").checked = true;
					}
					else {
						document.getElementById("wallCheckbox").checked = false;
					}
				}
			}

			function reloadSprite() {
				var drw = "SPR_" + drawingId;
				for (y in imageStore.source[drw]) {
					for (var x = 0; x < 8; x++) {
						var pixel = parseInt( imageStore.source[drw][y].charAt(x) );
						drawing_data[y][x] = pixel;
					}
				}
				drawPaintCanvas();

				if (drawingId in dialog) {
					document.getElementById("dialogText").value = dialog[drawingId];
				}
				else {
					document.getElementById("dialogText").value = "";
				}
			}

			function sortedTileIdList() {
				return Object.keys( tile ).sort();
			}

			function sortedSpriteIdList() {
				return Object.keys( sprite ).sort();
			}

			function map_onMouseDown(e) {
				var x = Math.floor(e.layerX / (tilesize*scale));
				var y = Math.floor(e.layerY / (tilesize*scale));
				console.log(x + " " + y);
				var row = set[curSet].tilemap[y];
				if (drawingId != null) {
					if (paintMode == TileType.Tile) {
						if ( row.charAt(x) === "0" ) {
							//add
							row = row.substr(0, x) + drawingId + row.substr(x+1);
						}
						else {
							//delete (better way to do this?)
							row = row.substr(0, x) + "0" + row.substr(x+1);
						}
						set[curSet].tilemap[y] = row;
					}
					else {
						sprite[drawingId].set = curSet;
						sprite[drawingId].x = x;
						sprite[drawingId].y = y;
						row = row.substr(0, x) + "0" + row.substr(x+1);
					}
					refreshGameData();
					drawEditMap();
				}
			}

			function paint_onMouseDown(e) {
				var x = Math.floor(e.layerX / paint_scale);
				var y = Math.floor(e.layerY / paint_scale);
				if (drawing_data[y][x] == 0) {
					curPaintBrush = 1;
				}
				else {
					curPaintBrush = 0;
				}
				drawing_data[y][x] = curPaintBrush;
				drawPaintCanvas();
				isPainting = true;
			}

			function paint_onMouseMove(e) {
				if (isPainting) {	
					var x = Math.floor(e.layerX / paint_scale);
					var y = Math.floor(e.layerY / paint_scale);
					drawing_data[y][x] = curPaintBrush;
					drawPaintCanvas();
				}
			}

			function paint_onMouseUp(e) {
				isPainting = false;
				saveDrawingData();
				refreshGameData();
				drawEditMap();
			}

			function drawPaintCanvas() {
				//background
				paint_ctx.fillStyle = "rgb("+palette[drawingPal][0][0]+","+palette[drawingPal][0][1]+","+palette[drawingPal][0][2]+")";
				paint_ctx.fillRect(0,0,canvas.width,canvas.height);

				//pixel color
				if (paintMode == TileType.Tile) {
					paint_ctx.fillStyle = "rgb("+palette[drawingPal][1][0]+","+palette[drawingPal][1][1]+","+palette[drawingPal][1][2]+")";
				}
				else if (paintMode == TileType.Sprite || paintMode == TileType.Avatar) {
					paint_ctx.fillStyle = "rgb("+palette[drawingPal][2][0]+","+palette[drawingPal][2][1]+","+palette[drawingPal][2][2]+")";
				}

				//draw pixels
				for (var x = 0; x < 8; x++) {
					for (var y = 0; y < 8; y++) {
						if (drawing_data[y][x] == 1) {
							paint_ctx.fillRect(x*paint_scale,y*paint_scale,1*paint_scale,1*paint_scale);
						}
					}
				}

				//draw grid
				if (drawPaintGrid) {
					paint_ctx.fillStyle = "#fff";
					for (var x = 1; x < tilesize; x++) {
						paint_ctx.fillRect(x*paint_scale,0*paint_scale,1,tilesize*paint_scale);
					}
					for (var y = 1; y < tilesize; y++) {
						paint_ctx.fillRect(0*paint_scale,y*paint_scale,tilesize*paint_scale,1);
					}
				}
			}

			function drawEditMap() {	
				//clear screen
				ctx.fillStyle = "rgb("+palette[curPal()][0][0]+","+palette[curPal()][0][1]+","+palette[curPal()][0][2]+")";
				ctx.fillRect(0,0,canvas.width,canvas.height);

				//draw map
				drawSet(set[curSet]);

				//draw grid
				if (drawMapGrid) {
					ctx.fillStyle = "#fff";
					for (var x = 1; x < mapsize; x++) {
						ctx.fillRect(x*tilesize*scale,0*tilesize*scale,1,mapsize*tilesize*scale);
					}
					for (var y = 1; y < mapsize; y++) {
						ctx.fillRect(0*tilesize*scale,y*tilesize*scale,mapsize*tilesize*scale,1);
					}
				}
			}

			function saveDrawingData() {
				if (paintMode == TileType.Tile) {
					//create tile if it doesn't exist
					var drw = "TIL_" + drawingId;
					if (!(drawingId in tile)) {
						tile[drawingId] = {
							drw : drw,
							col : 1
						};
					}
					//save tile drawing
					imageStore.source[drw] = [];
					for (var y = 0; y < 8; y++) {
						var ln = "";
						for (var x = 0; x < 8; x++) {
							ln += drawing_data[y][x];
						}
						imageStore.source[drw].push(ln);
					} 
					renderImages(); //rerender all images (inefficient)
				}
				else { //paintMode is Sprite or Avatar
					//new sprite
					var drw = "SPR_" + drawingId;
					if (!(drawingId in sprite)) {
						sprite[drawingId] = { //todo create default sprite creation method
							drw : drw,
							col : 2,
							set : null,
							x : -1,
							y : -1
						};
					}
					//save sprite drawing
					imageStore.source[drw] = [];
					for (var y = 0; y < 8; y++) {
						var ln = "";
						for (var x = 0; x < 8; x++) {
							ln += drawing_data[y][x];
						}
						imageStore.source[drw].push(ln);
					} 
					renderImages();
				}
			}

			function refreshGameData() {
				var gameData = serializeWorld();
				document.getElementById("game_data").value = gameData;
			}

			function on_edit_mode() {
				stopGame();
				parseWorld(document.getElementById("game_data").value); //reparse world to account for any changes during gameplay
				drawEditMap();
				listenMapEditEvents();
			}

			function on_play_mode() {
				unlistenMapEditEvents();
				load_game(document.getElementById("game_data").value);
			}

			function toggleGrid() {
				drawPaintGrid = !drawPaintGrid;
				drawPaintCanvas();
			}

			function toggleMapGrid() {
				drawMapGrid = !drawMapGrid;
				drawEditMap();
			}

			function on_change_title() {
				title = document.getElementById("titleText").value;
				refreshGameData();
			}

			function on_change_color_bg() {
				var rgb = hexToRgb( document.getElementById("backgroundColor").value );
				palette[drawingPal][0][0] = rgb.r;
				palette[drawingPal][0][1] = rgb.g;
				palette[drawingPal][0][2] = rgb.b;
				refreshGameData();
				renderImages();
				drawPaintCanvas();
				drawEditMap();
			}

			function on_change_color_tile() {
				var rgb = hexToRgb( document.getElementById("tileColor").value );
				palette[drawingPal][1][0] = rgb.r;
				palette[drawingPal][1][1] = rgb.g;
				palette[drawingPal][1][2] = rgb.b;
				refreshGameData();
				renderImages();
				drawPaintCanvas();
				drawEditMap();
			}

			function on_change_color_sprite() {
				var rgb = hexToRgb( document.getElementById("spriteColor").value );
				palette[drawingPal][2][0] = rgb.r;
				palette[drawingPal][2][1] = rgb.g;
				palette[drawingPal][2][2] = rgb.b;
				refreshGameData();
				renderImages();
				drawPaintCanvas();
				drawEditMap();
			}

			//hex-to-rgb method borrowed from stack overflow
			function hexToRgb(hex) {
				// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
				var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
				hex = hex.replace(shorthandRegex, function(m, r, g, b) {
					return r + r + g + g + b + b;
				});

				var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
				return result ? {
					r: parseInt(result[1], 16),
					g: parseInt(result[2], 16),
					b: parseInt(result[3], 16)
				} : null;
			}

			function on_paint_avatar() {
				paintMode = TileType.Avatar;
				drawingId = "A";
				reloadSprite();
				document.getElementById("dialog").setAttribute("style","display:none;");
				document.getElementById("wall").setAttribute("style","display:none;");
				document.getElementById("paintNav").setAttribute("style","display:none;");
			}
			function on_paint_tile() {
				paintMode = TileType.Tile;
				tileIndex = 0;
				drawingId = sortedTileIdList()[tileIndex];
				reloadTile();
				document.getElementById("dialog").setAttribute("style","display:none;");
				document.getElementById("wall").setAttribute("style","display:block;");
				document.getElementById("paintNav").setAttribute("style","display:block;");
			}
			function on_paint_sprite() {
				paintMode = TileType.Sprite;
				spriteIndex = 1;
				drawingId = sortedSpriteIdList()[spriteIndex];
				reloadSprite();
				document.getElementById("dialog").setAttribute("style","display:block;");
				document.getElementById("wall").setAttribute("style","display:none;");
				document.getElementById("paintNav").setAttribute("style","display:block;");
			}

			function on_change_dialog() {
				dialog[drawingId] = document.getElementById("dialogText").value;
				refreshGameData();
			}

			function on_game_data_change() {
				//todo - doesn't work perfectly, probably need to clear out the whole game world
				parseWorld(document.getElementById("game_data").value); //reparse world if user directly manipulates game data
				drawPaintCanvas();
				drawEditMap();
			}

			function on_toggle_wall() {
				if ( document.getElementById("wallCheckbox").checked ){
					//add to wall list
					set[curSet].walls.push( drawingId );
				}
				else if ( set[curSet].walls.indexOf(drawingId) != -1 ){
					//remove from wall list
					set[curSet].walls.splice( set[curSet].walls.indexOf(drawingId), 1 );
				}
				console.log(set[curSet]);
				refreshGameData();
			}


			var engineScript;
			function loadEngineScript() {
				var client = new XMLHttpRequest();
				client.open('GET', './bitsy.js');
				client.onreadystatechange = function() {
				  engineScript = client.responseText;
				}
				client.send();
			}
			var webExportTemplate = "<!DOCTYPE HTML>\n<html>\n<head>\n<title>@@T</title>\n<style>\nhtml {height:592px;}\nbody {width:100%; height:100%; overflow:hidden;}\n#game {background:black;margin: 0 auto;margin-top: 40px;display: block;}\n</style>\n<script>\n@@E\n<\/script>\n</head>\n<body onload='startExportedGame()'>\n<canvas id='game'>\n</canvas>\n</body>\n</html>";
				
			function exportGame() {
				refreshGameData(); //just in case
				var gameData = document.getElementById("game_data").value; //grab game data
				gameData = gameData.split("\n").join("\\n"); //replace newlines with escaped newlines
				var html = webExportTemplate.substr(); //copy template
				var titleIndex = html.indexOf("@@T");
				html = html.substr(0,titleIndex) + title + html.substr(titleIndex+3);
				var engineIndex = html.indexOf("@@E");
				html = html.substr(0,engineIndex) + engineScript + html.substr(engineIndex+3);
				var gameDataIndex = html.indexOf("@@D");
				html = html.substr(0,gameDataIndex) + gameData + html.substr(gameDataIndex+3);
				downloadFile("mygame.html",html);
			}

			function downloadFile(filename, text) {
				var element = document.createElement('a');
				element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
				element.setAttribute('download', filename);

				element.style.display = 'none';
				document.body.appendChild(element);

				element.click();

				document.body.removeChild(element);
			}

			function hideAbout() {
				document.getElementById("about").setAttribute("style","display:none;");
			}
		</script>
	</head>

	<body onload="start()">
		<div id="topbar">
			bitsy editor v0
		</div>
		<div class="panel" id="about">
			~ about bitsy ~ <br>
			hi! bitsy is a little editor for little games or worlds. <br>
			the goal is to make it easy to make games where you <br>
			can walk around and talk to people and be somewhere. <br>
			<br>
			you can draw things in the ~ paint ~ panel, then place <br>
			them in your world in the ~ map ~ panel. you can write <br>
			dialog for your characters (aka sprites) too. <br>
			<br>
			some words:<br>
			* map - the game world<br>
			* avatar - the player's character<br>
			* tile - a piece of the scenery<br>
			* sprite - other characters or things<br>
			<br>
			to try your game, switch the map to play mode: <br>
			* walk around with the arrow keys <br>
			* talk to sprites by walking up to them <br>
			<br>
			use "share game" to download the game as an html file. <br>
			you can email the file to a friend, or host it online! <br>
			<br>
			if you want to talk about bitsy, report a bug, or share <br>
			a game you made, i'm on twitter <a href="https://www.twitter.com/adamledoux">@adamledoux</a><br>
			<br>
			bitsy v0 was tested in chrome, so if you use another <br>
			browser, some things might not work right. <br>
			<br>
			special thanks to: <br>
			* mary-margaret <br>
			* seattle game meetup peeps <br>
			<br>
			use it in good health!
			<br>
			- adam
			<br>
			<br>
			<button onclick="hideAbout();"> ok, hide this nonsense </button>
		</div>
		<div class="panel">
			~ map ~
			<form>
				<input type="radio" name="edit mode" value="edit" onclick="on_edit_mode()" checked> edit
				<input type="radio" name="edit mode" value="play" onclick="on_play_mode()"> play
			</form>
			<canvas id="game"></canvas>
			<br>
			<button onclick="toggleMapGrid();"> show/hide tile grid </button>
		</div>
		<div class="panel">
			~ paint ~ <br>
			<form>
				<input type="radio" name="paint mode" value="sprite" onclick="on_paint_avatar();" checked> avatar
				<input type="radio" name="paint mode" value="tile" onclick="on_paint_tile();"> tile
				<input type="radio" name="paint mode" value="sprite" onclick="on_paint_sprite();"> sprite
			</form>
			<div id="paintNav" style="display:none;">
				<button onclick="prev();"> < prev </button>
				<button onclick="next();"> next > </button>
				<button onclick="newDrawing();"> + new </button>
			</div>
			<canvas id="paint"></canvas>
			<br>
			<div id="wall" style="display:none;">is this tile a wall? <input type="checkbox" id="wallCheckbox" onchange="on_toggle_wall();"> </div>
			<div id="dialog" style="display:none;">dialog: <input type="text" id="dialogText" onchange="on_change_dialog();"></div>
			<button onclick="toggleGrid();"> show/hide pixel grid </button>
		</div>
		<div class="panel">
			~ colors ~ <br>
			<input type="color" value="#0052cc" id="backgroundColor" onchange="on_change_color_bg();"> background <br>
			<input type="color" value="#809fff" id="tileColor" onchange="on_change_color_tile();"> tile <br>
			<input type="color" value="#ffffff" id="spriteColor" onchange="on_change_color_sprite();"> sprite <br>
		</div>
		<div class="panel">
			~ title and share ~ <br>
			title: <input type="text" id="titleText" onchange="on_change_title();" value="Write title here"> <br>
			<button onclick="exportGame();">share game</button>
		</div>
		<div class="panel">
			~ game data ~ <br>
			<textarea id="game_data" style="width:300px;height:300px;" onchange="on_game_data_change();"></textarea>
	</body>
</html>